package epam_java.jsontest;

import epam_java.telephone.users.AbstractUser;
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class JsonParser {

    /**
     * @return the isclassStarted
     */
    public static boolean isIsclassStarted() {
        return isclassStarted;
    }

    /**
     * @param aIsclassStarted the isclassStarted to set
     */
    public static void setIsclassStarted(boolean aIsclassStarted) {
        isclassStarted = aIsclassStarted;
    }

    /**
     * @return the isclassFinished
     */
    public static boolean isIsclassFinished() {
        return isclassFinished;
    }

    /**
     * @param aIsclassFinished the isclassFinished to set
     */
    public static void setIsclassFinished(boolean aIsclassFinished) {
        isclassFinished = aIsclassFinished;
    }
   private ArrayList<Object> readObjects;
   private ArrayList<FieldObject> fields;
   private  File f;
   /**
    * return true if parsing of class has been finished
    */
   private static boolean isclassStarted;
   private static boolean isclassFinished;
   private int currentLine=0;
   private String value,encoding;
    JsonParser(String filepath,String encoding){
        this.f=new File(filepath);
        this.encoding=encoding;
        readObjects=new ArrayList<Object>();
    }
    void parse(Object obj) throws InstantiationException, IllegalAccessException,JsonParserException, FailureSetterException, ClassNotFoundException{

        Object newObj=obj.getClass().newInstance();
        getFields(obj);
        Scanner scanner=null;
        try{
            scanner=new Scanner(f,encoding);
            while(scanner.hasNextLine()){
                String nextline=scanner.nextLine().trim();
                if(nextline.compareTo("")!=0){
                   if(nextline.compareTo("{")==0){
                       setIsclassStarted(true);
                       setIsclassFinished(false);
                   } else
                   if(nextline.compareTo("}")==0||nextline.compareTo("},")==0){
                       if(allClassFieldsDefined()){
                       setIsclassFinished(true);
                       setIsclassStarted(false);
                       } else throw new JsonParserException("Not all fields has"+
                               " been defined ad line= "+nextline+currentLine);
                   } else
                   {
                      if(allClassFieldsDefined()) throw
                              new JsonParserException("All fields has been alreay"
                              + "defined at line= "+ currentLine);
                      String fieldname=getFieldName(nextline);
                      FieldObject field=getFieldObjectByName(fieldname);
                      field.setValue(parseObjectByType(field.getField().getType().
                             getName() , nextline));
                      field.setHasFind(true);
                   }
                }
                if(isIsclassFinished()){
                    Object object=makeObject(obj.getClass());
                    getReadObjects().add(object);
                    resetObjectFields();
                }
                currentLine++;
            }
            
        } catch(FileNotFoundException fexep){

        }
        finally{
           if(scanner!=null) {
               scanner.close();
           }
        }
     }
    /**
     * fill fields with this class object fields
     */
   void getFields(Object obj){
       Class cl=obj.getClass();
       Field[] fiel=cl.getDeclaredFields();
       AccessibleObject.setAccessible(fiel, true);
       fields=new ArrayList<FieldObject>();
       for(Field f:fiel){
           fields.add(new FieldObject(null, f, false));
       }
   }
   /**
    * return true if all class field has been alredy
    * reads from file and parsing
    * @return
    */
   boolean allClassFieldsDefined(){
       for(FieldObject obj:fields){
                if(!obj.isHasfind()) return false;
       }
       return true;
   }
   void resetObjectFields(){
       for(FieldObject fiel:fields){
          fiel.setHasFind(false);
       }
   }
   String getFieldName(String readString) throws JsonParserException{
       Pattern p=Pattern.compile("^\\\"([a-zA-z]+)\\\":.+$");
       Matcher m = p.matcher(readString);
       if(m.find()){
           return m.group(1);
       } else throw new JsonParserException("Not right string format in "+
               readString+ " at line number= " + currentLine);
   }
   /**
    *
    * @param name
    * @return fieldObject from fields which name equals param
    * @throws JsonParserException if such field not found throws this
    * exception
    */
  private FieldObject getFieldObjectByName(String name) throws JsonParserException{
           FieldObject field=null;
      for(FieldObject fiel:fields){
      //    System.out.println(""+fiel.getField().getName());
           if(fiel.getField().getName().compareTo(name)==0){
               field=fiel;
               break;
           }
      }
      if(field!=null) return field; else
            {
           throw new JsonParserException("Such field as \"" + name + "\" not "
           +"consists in requering class at line =" +currentLine);
       }
   }
/**
 * this method parce line and try find value of class cl
 * cl can be int,byte,long, short,double,float,String
 * Arrays of String,double and int.
 * if cl is primitive type it wrap value in class-conteiner and return
 * if cl is some other class or parcing failed it
 * throw JsonParserException
 * @param cl
 * @param line
 * @return
 * @throws JsonParserException
 */
private  Object parseObjectByType(String cl,String line) throws JsonParserException, ClassNotFoundException{
          if(cl.compareTo(int.class.getName())==0){
              return new Integer(ParcerFactory.parceInt(line));
          }
          if(cl.compareTo(long.class.getName())==0) return new Long(ParcerFactory.parceLong(line));
          if(cl.compareTo(byte.class.getName())==0) return new Byte((byte)ParcerFactory.parceInt(line));
          if(cl.compareTo(short.class.getName())==0) return new Short((short)ParcerFactory.parceInt(line));
           else if(cl.compareTo(double.class.getName())==0||
                   cl.compareTo(float.class.getName())==0){
              return new Double(ParcerFactory.parceDouble(line));
          } else if (cl.compareTo(boolean.class.getName())==0){
              return new Boolean(ParcerFactory.parceBoolean(line));
          } else if(cl.compareTo(char.class.getName())==0){
              return new Character(ParcerFactory.parceChar(line));
          }
          if(cl.compareTo(String.class.getName())==0) {
              return ParcerFactory.parceString(line);
          } else
           if(Class.forName(cl).isArray()){
               if(Class.forName(cl).getComponentType()==String.class){
                   return ParcerFactory.parceStringArray(line);
               } else
               if(Class.forName(cl).getComponentType()==double.class){
                   return ParcerFactory.parceDoubleArray(line);
               } else
               if(Class.forName(cl).getComponentType()==int.class){
                   return ParcerFactory.parceIntArray(line);
               }
           } else {
          throw new JsonParserException("Sorry but that type "
                   + cl +" not provided");
           }
      return null;
  }
 Object makeObject(Class cl) throws InstantiationException, IllegalAccessException,
 FailureSetterException{
     Object  newobj=cl.newInstance();
     for(FieldObject fiel:fields){
             StringBuilder builder=new StringBuilder("set");
             builder.append(fiel.getField().getName().toUpperCase().charAt(0));
             builder.append(fiel.getField().getName()
             .substring(1,fiel.getField().getName().length()));
                try {
                    Method meth = cl.getMethod(builder.toString(), fiel.getValue().getClass());
                    meth.invoke(newobj,fiel.getValue());
                } catch (NoSuchMethodException ex) {
                    throw new FailureSetterException("Setter "+builder.toString()+
                            " is not available");
                } catch (SecurityException ex) {
                    throw new FailureSetterException("Setter "+builder.toString()+
                            " is not public");
                } catch(IllegalArgumentException ex){
                    System.out.println(""+ex.getMessage());
                    ex.printStackTrace();}
                  catch(InvocationTargetException ex){
                      System.out.println(""+ex.getMessage());
                      ex.printStackTrace();}
     }
     return newobj;
 }

    /**
     * @return the readObjects
     */
    public ArrayList<Object> getReadObjects() {
        return readObjects;
    }
}